/*
 * Created on 2005-7-11
 *
 */
package com.et114.components.ldap4oj.core;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.springframework.dao.DataAccessException;

import com.et114.components.ldap4oj.support.CanonicalLdapExceptionTranslater;
import com.et114.components.ldap4oj.support.ContextSourceInterface;
import com.et114.components.ldap4oj.support.LdapExceptionTranslater;
import com.et114.components.ldap4oj.support.LdapUtils;


public class LdapTemplate {
    

    private ContextSourceInterface contextSource;

    private LdapExceptionTranslater exceptionTranslater = null;

    /**
     * The basic no arg constructor
     */
    public LdapTemplate() {
    }

    /**
     * The constructor with a given LDAP location via a contextSource
     * 
     * @param contextSource
     *            the DirContext provider implementation
     */
    public LdapTemplate(ContextSourceInterface contextSource) {
        this.contextSource = contextSource;
    }

    /**
     * @return Returns the contextSource.
     */
    public ContextSourceInterface getContextSource() {
        return contextSource;
    }

    /**
     * @param contextSource
     *            The contextSource to set.
     */
    public void setContextSource(ContextSourceInterface contextSource) {
        this.contextSource = contextSource;
    }

    /**
     * @return Returns the exceptionTranslater.
     */
    public LdapExceptionTranslater getExceptionTranslater() {
        if (exceptionTranslater == null) {
            exceptionTranslater = new CanonicalLdapExceptionTranslater();
        }
        return exceptionTranslater;
    }

    /**
     * @param exceptionTranslater
     *            The exceptionTranslater to set.
     */
    public void setExceptionTranslater(
            LdapExceptionTranslater exceptionTranslater) {
        this.exceptionTranslater = exceptionTranslater;
    }

    /*
     * Generic access method
     */

    /**
     * Most generic execution method which is configured with a callback to
     * which we provide a DirContext instance from the contextSource. The
     * DirContext instance is then freed and LDAP exceptions, if any, are
     * translated to the generic dao ones, using the exceptionTranslater
     * instance.
     * 
     * @param action
     *            callback given the dirContext instance
     * @return the return value of the doInAction method.
     */
    public Object execute(final OperationCallback action) {
        DirContext context = getContextSource().getDirContext();
        try {
            Object result = action.doInAction(context);
            return result;
        } catch (NamingException ex) {
            throw getExceptionTranslater().translate(
                    "executing OperationCallback", ex);
        } finally {
            LdapUtils.closeContext(context);
        }
    }

    /*
     * Access method for Context queries with callback handler
     */
    public Object search(final String name, final String filter,
            final SearchControls searchControls,
            final SearchResultCallbackHandler callback) {
        return this.execute(new OperationCallback() {
            public Object doInAction(DirContext context)
                    throws NamingException, DataAccessException {
                return parseNamingEnumeration(context.search(name, filter,
                        searchControls), callback);
            }
        });
    }

    public Object search(final String name, final String filterExpr,
            final Object[] filterArgs, final SearchControls cons,
            final SearchResultCallbackHandler callback) {
        return this.execute(new OperationCallback() {
            public Object doInAction(DirContext context)
                    throws NamingException, DataAccessException {
                return parseNamingEnumeration(context.search(name, filterExpr,
                        filterArgs, cons), callback);
            }
        });
    }

    public Object search(final String name,
            final Attributes matchingAttributes,
            final SearchResultCallbackHandler callback) {
        return search(name, matchingAttributes, null, callback);
    }

    public Object search(final String name,
            final Attributes matchingAttributes,
            final String[] attributesToReturn,
            final SearchResultCallbackHandler callback) {
        return this.execute(new OperationCallback() {
            public Object doInAction(DirContext context)
                    throws NamingException, DataAccessException {
                return parseNamingEnumeration(context.search(name,
                        matchingAttributes, attributesToReturn), callback);
            }
        });
    }
    
    public Object createSubcontext(final String nodeName,final Attributes addAttributes,
            final SearchResultCallbackHandler callback) {
        return this.execute(new OperationCallback() {
            public Object doInAction(DirContext context)
                    throws NamingException, DataAccessException {
            	return context.createSubcontext(nodeName,addAttributes);
            }
        });
    }
    
    public Object addObject(final String dn,final Object leafObject,final Attributes addAttributes,
            final SearchResultCallbackHandler callback) {
        return this.execute(new OperationCallback() {
            public Object doInAction(DirContext context)
                    throws NamingException, DataAccessException {
            	 context.bind(dn,leafObject,addAttributes);
            	 return null;
            }
        });
    }
    
    


    /**
     * Internal factorised method which iterates over a NamingEnumeration,
     * applying the SearchResultCallbackHandler method on each SearchResult and
     * finally free the enumeration itself.
     * 
     * @param namingEnumeration
     *            the enumeration to iterate through
     * @param callback
     *            the callback to apply on each result
     * @return the result value of the callback getResult() method
     * @throws NamingException
     *             raised for any LDAP problem
     */
    private Object parseNamingEnumeration(NamingEnumeration namingEnumeration,
            SearchResultCallbackHandler callback) throws NamingException {
        try {
            while (namingEnumeration.hasMore()) {
                callback.processSearchResult((SearchResult) namingEnumeration
                        .next());
            }
            return callback.getResult();
        } finally {
            namingEnumeration.close();
        }
    }


}
